<?php

declare(strict_types=1);

namespace App\Aspect;

use App\Exception\BusinessException;
use App\Exception\Handler\BusinessExceptionHandler;
use App\Kernel\Http\Response;
use App\Middleware\CorsMiddleware;
use App\Utils\Log;
use Hyperf\Di\Annotation\Aspect;
use Hyperf\Di\Aop\AbstractAspect;
use Hyperf\Di\Aop\ProceedingJoinPoint;
use Hyperf\Di\Exception\Exception;
use Hyperf\HttpServer\Router\Dispatched;
use Hyperf\HttpServer\Router\Handler;
use Hyperf\HttpServer\Router\Router;
use Hyperf\Validation\ValidationException;
use Psr\Container\ContainerInterface;
use Psr\Http\Message\ServerRequestInterface;
use Throwable;

/**
 * @Aspect
 */
class LogRecord extends AbstractAspect
{
    protected $container;

    /**
     * @var ProceedingJoinPoint
     */
    protected $point;

    public $classes = [
        'App\Kernel\Http\Response::success',
        'App\Kernel\Http\Response::fail',
        'App\Exception\Handler\BusinessExceptionHandler::handle',
        'App\Middleware\CorsMiddleware::process'
    ];

    /**
     * LogRecord constructor.
     * @param ContainerInterface $container
     */
    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    /**
     * @param ProceedingJoinPoint $proceedingJoinPoint
     * @return mixed
     * @throws Exception
     */
    public function process(ProceedingJoinPoint $proceedingJoinPoint)
    {
        $this->point = $proceedingJoinPoint;
        switch ($proceedingJoinPoint->getReflectMethod()->class) {
            case CorsMiddleware::class:
                $this->request();
                break;
            case Response::class:
                $this->response();
                break;
            case BusinessExceptionHandler::class:
                $this->exception();
                break;
        }
        return $proceedingJoinPoint->process();
    }

    public function request()
    {
        /** @var ServerRequestInterface $request */
        [$request] = $this->point->getArguments();
        $data = [];
        $data['fullUrl'] = (string)$request->getUri();

        /** @var Dispatched $dispatched */
        $dispatched = $request->getAttribute(Dispatched::class);
        if ($dispatched->isFound()) {
            // 不记录服务健康检测请求记录
            $parseUrl = parse_url($data['fullUrl']);
            if (isset($parseUrl['path']) && trim($parseUrl['path'], '/') == 'heartbeat') {
                unset($parseUrl);
            } else {
                $data['method'] = $request->getMethod();
                $data['params']['post'] = $request->getParsedBody();
                $data['params']['get'] = $request->getQueryParams();

                $message = sprintf('[request] %s', json_encode($data, JSON_UNESCAPED_SLASHES));
                Log::get()->info($message);
            }
        }
    }

    public function response()
    {
        switch ($this->point->getReflectMethod()->name) {
            case 'success':
                /** @var mixed $data */
                [$data] = $this->point->getArguments();
                $formatMessage = $this->resFormat(0, $data);
                break;
            case 'fail':
                [$code, $message] = $this->point->getArguments();
                $formatMessage = $this->resFormat($code, $message);
                break;
            default:
                $formatMessage = $this->resFormat(0, 'success');

        }
        Log::get()->info($formatMessage);
    }

    public function exception()
    {
        /** @var Throwable $throwable */
        [$throwable] = $this->point->getArguments();
        if (!($throwable instanceof ValidationException) && !($throwable instanceof BusinessException)) {
            Log::get()->error((string)$throwable);
        }
    }

    /**
     * @param int $code
     * @param string|array $message
     * @return string
     */
    public function resFormat(int $code, $message): string
    {
        $data = [];
        if ($code == 0) {
            if (is_bool($message)) {
                $message = $message ? 'true' : 'false';
            }
            $data['code'] = $code;
            $data['data'] = $message;
        } else {
            $data['code'] = $code;
            $data['message'] = $message;
        }
        $message = json_encode($data, JSON_UNESCAPED_UNICODE);
        return sprintf('[response] %s', $message);
    }

}
